﻿
#include <Math\Vector2.h>
#include <Math\Vector3.h>
#include <Math\Matrix4.h>
#include <Engine\Camera.h>
#include <Engine\Utilities.h>
#include <Engine\Mesh.h>
#include <Resource\ObjLoader.h>

#include <FreeImage.h>

#define WINDOW_TITLE_PREFIX "Chapter 4"

int CurrentWidth = 800,
	CurrentHeight = 600,
	WindowHandle = 0;
float	CarmeaPosX = 0,
	CarmeaPosY = 250,
	CarmeaPosZ = 0;

unsigned FrameCount = 0;

Camera GCamer=Camera(60,(float)CurrentWidth / CurrentHeight,1.0f,100.0f);
starMesh mesh;

GLuint
	ProjectionMatrixUniformLocation,
	ViewMatrixUniformLocation,
	ModelMatrixUniformLocation,
	VAOID,
	VBOID[2] = { 0 },
	ShaderIds[3] = { 0 },
	tid;

Matrix
	ProjectionMatrix,
	ModelMatrix;

float CubeRotation = 0;
clock_t LastTime = 0;

void Initialize(int, char*[]);
void InitWindow(int, char*[]);
void ResizeFunction(int, int);
void RenderFunction(void);
void TimerFunction(int);
void IdleFunction(void);
void CreateCube(void);
void DestroyCube(void);
void DrawCube(void);
void KeyboardFunction(unsigned char, int, int);


void CreateMesh();
void LoadTexture();
void DestroyMesh();
void DrawMesh();

void LoadTexture()
{
	FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
	//pointer to the image, once loaded
	FIBITMAP *dib(0);
	//pointer to the image data
	BYTE* bits(0);
	//image width and height
	unsigned int width(0), height(0);
	
	const char* filename = "orc.png";
	//check the file signature and deduce its format
	fif = FreeImage_GetFileType(filename, 0);
	//if still unknown, try to guess the file format from the file extension
	if(fif == FIF_UNKNOWN) 
		fif = FreeImage_GetFIFFromFilename(filename);
	//if still unkown, return failure
	if(fif == FIF_UNKNOWN)
		return;

	//check that the plugin has reading capabilities and load the file
	if(FreeImage_FIFSupportsReading(fif)){
		dib = FreeImage_Load(fif, filename);
	}
	//if the image failed to load, return failure
	if(!dib){
		return;
	}
	
	int colorType = FreeImage_GetColorType(dib);

	//retrieve the image data
	bits = FreeImage_GetBits(dib);
	//get the image width and height
	width = FreeImage_GetWidth(dib);
	height = FreeImage_GetHeight(dib);
	//if this somehow one of these failed (they shouldn't), return failure
	if((bits == 0) || (width == 0) || (height == 0))
		return;


	//glPixelStorei(GL_UNPACK_ALIGNMENT, FreeImage_GetLine(dib));

	glActiveTexture(GL_TEXTURE0);
	glUseProgram(ShaderIds[0]);

	glGenTextures(1, &tid);
	glBindTexture(GL_TEXTURE_2D, tid);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width,height, 0, GL_RGB, GL_UNSIGNED_BYTE,bits);
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width,height, 0, GL_BGRA, GL_UNSIGNED_BYTE,bits);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	ExitOnGLError("ERROR: Could not set Tex");
	// Set the Tex1 sampler uniform to refer to texture unit 0
	int loc = glGetUniformLocation(ShaderIds[0], "Tex1");
	ExitOnGLError("ERROR: Could not set Tex");
	if( loc >= 0 )
	{
		glUniform1i(loc, 0);
		ExitOnGLError("ERROR: Could not set Tex");
	}
	else
	{
		fprintf(stderr, "Uniform variable Tex1 not found!\n");
	}

	glUseProgram(0);

}

int KeyTime;
int LastKeyTime;
void KeyboardFunction(unsigned char Key, int X, int Y)
{
	switch (Key)
	{

	case 'a':
		{
			GCamer.Move(starVector3(0.5,0,0));
			break;
		}
	case 'd':
		{
			GCamer.Move(starVector3(-0.5,0,0));
			break;
		}
	case 'w':
		{
			GCamer.Move(starVector3(0.0,0,0.5));
			break;
		}
	case 's':
		{
			GCamer.Move(starVector3(0.0,0,-0.5));
			break;
		}
	default:
		break;
	}
}

void InitWindow(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutSetKeyRepeat(GLUT_KEY_REPEAT_DEFAULT);
	
	glutInitContextVersion(4, 0);
	glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);
	glutInitContextProfile(GLUT_CORE_PROFILE);
	glutIdleFunc(IdleFunction);
	glutTimerFunc(0, TimerFunction, 0);

	glutCloseFunc(DestroyMesh);

	glutSetOption(
		GLUT_ACTION_ON_WINDOW_CLOSE,
		GLUT_ACTION_GLUTMAINLOOP_RETURNS
	);
	
	glutInitWindowSize(CurrentWidth, CurrentHeight);

	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

	WindowHandle = glutCreateWindow(WINDOW_TITLE_PREFIX);

	if(WindowHandle < 1) {
		fprintf(
			stderr,
			"ERROR: Could not create a new rendering window.\n"
		);
		exit(EXIT_FAILURE);
	}

	glutReshapeFunc(ResizeFunction);
	glutDisplayFunc(RenderFunction);
	glutKeyboardFunc(KeyboardFunction);
}

void Initialize(int argc, char* argv[])
{
	GLenum GlewInitResult;

	InitWindow(argc, argv);
	glewExperimental = GL_TRUE; 

	GlewInitResult = glewInit();

	if (GLEW_OK != GlewInitResult) {
		fprintf(
			stderr,
			"ERROR: %s\n",
			glewGetErrorString(GlewInitResult)
		);
		exit(EXIT_FAILURE);
	}
	
	fprintf(
		stdout,
		"INFO: OpenGL Version: %s\n",
		glGetString(GL_VERSION)
	);

	glGetError();
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	ExitOnGLError("ERROR: Could not set OpenGL depth testing options");

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);
	ExitOnGLError("ERROR: Could not set OpenGL culling options");

	//ModeMatrix = IDENTITY_MATRIX;
	ProjectionMatrix = IDENTITY_MATRIX; 
	GCamer.SetMoveSpeed(0.1f);


	CreateMesh();
}



void ResizeFunction(int Width, int Height)
{
	CurrentWidth = Width;
	CurrentHeight = Height;
	glViewport(0, 0, CurrentWidth, CurrentHeight);
	ProjectionMatrix =
		CreateProjectionMatrix(
		60,
		(float)CurrentWidth / CurrentHeight,
		1.0f,
		100.0f
		); 

	glUseProgram(ShaderIds[0]);
	glUniformMatrix4fv(ProjectionMatrixUniformLocation, 1, GL_FALSE, ProjectionMatrix.m);
	glUseProgram(0);
}

void RenderFunction(void)
{
	++FrameCount;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	DrawMesh();

	glutSwapBuffers();
	glutPostRedisplay();
}

void IdleFunction(void)
{
    glutPostRedisplay();
}
 
void TimerFunction(int Value)
{
    if (0 != Value) {
        char* TempString = (char*)
            malloc(512 + strlen(WINDOW_TITLE_PREFIX));
 
        sprintf(
            TempString,
            "%s: %d Frames Per Second @ %d x %d",
            WINDOW_TITLE_PREFIX,
            FrameCount * 4,
            CurrentWidth,
            CurrentHeight
        );
 
        glutSetWindowTitle(TempString);
        free(TempString);
    }
     
    FrameCount = 0;

    glutTimerFunc(1000, TimerFunction, 1);
}

void CreateMesh()
{
	ShaderIds[0] = glCreateProgram();
	ExitOnGLError("ERROR: Could not create the shader program");

	ShaderIds[1] = LoadShader("ch4_1_fragment.glsl", GL_FRAGMENT_SHADER);
	ShaderIds[2] = LoadShader("ch4_1_vertex.glsl", GL_VERTEX_SHADER);
	glAttachShader(ShaderIds[0], ShaderIds[1]);
	GLenum ErrorValue = glGetError();
	glAttachShader(ShaderIds[0], ShaderIds[2]);

	glLinkProgram(ShaderIds[0]);
	ExitOnGLError("ERROR: Could not link the shader program");

	ModelMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ModelMatrix");
	ViewMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ViewMatrix");
	ProjectionMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ProjectionMatrix");
	ExitOnGLError("ERROR: Could not get the shader uniform locations");


	ObjFileLoader::Load("ori2.obj",&mesh);

	glGenVertexArrays(1, &VAOID);
	ExitOnGLError("ERROR: Could not generate the VAO");
	glBindVertexArray(VAOID);
	ExitOnGLError("ERROR: Could not bind the VAO");

	glGenBuffers(2, VBOID);

	glBindBuffer(GL_ARRAY_BUFFER, VBOID[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(mesh.vertices[0])*mesh.vertexNum, (GLvoid*)mesh.vertices, GL_STATIC_DRAW);
	ExitOnGLError("ERROR: Could not bind the VBO to the VAO");

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(starVertex), (GLvoid*)0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(mesh.vertices[0]), (GLvoid*)(sizeof(mesh.vertices[0].xyz)+sizeof(mesh.vertices[0].normal)));
	glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(mesh.vertices[0]), (GLvoid*)(sizeof(mesh.vertices[0].xyz)+sizeof(mesh.vertices[0].normal)+sizeof(mesh.vertices[0].textureCoord)));
	ExitOnGLError("ERROR: Could not enable vertex attributes");
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glEnableVertexAttribArray(2);
	ExitOnGLError("ERROR: Could not enable vertex attributes");

	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBOID[1]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(mesh.indices[0])*mesh.indexNum,  (GLvoid*)mesh.indices, GL_STATIC_DRAW);
	ExitOnGLError("ERROR: Could not bind the IBO to the VAO");



	glBindVertexArray(0); 

	LoadTexture();

}
void DestroyMesh(void)
{
	glDetachShader(ShaderIds[0], ShaderIds[1]);
	glDetachShader(ShaderIds[0], ShaderIds[2]);
	glDeleteShader(ShaderIds[1]);
	glDeleteShader(ShaderIds[2]);
	glDeleteProgram(ShaderIds[0]);
	ExitOnGLError("ERROR: Could not destroy the shaders");

	glDeleteBuffers(2, VBOID);
	glDeleteVertexArrays(1,&VAOID);
	ExitOnGLError("ERROR: Could not destroy the buffer objects");
}



void DrawMesh()
{

	float CubeAngle;
	clock_t Now = clock();
	if (LastTime == 0)
		LastTime = Now;

	CubeRotation += 45.0f * ((float)(Now - LastTime) / CLOCKS_PER_SEC);
	CubeAngle = DegreesToRadians(CubeRotation);
	LastTime = Now;

	ModelMatrix = IDENTITY_MATRIX;
	RotateAboutY(&ModelMatrix, CubeAngle);
	//RotateAboutY(&ModelMatrix, 0);
	//RotateAboutX(&ModelMatrix, CubeAngle);

	ExitOnGLError("ERROR: Could not use the shader program");
	glUseProgram(ShaderIds[0]);
	ExitOnGLError("ERROR: Could not use the shader program");

	glUniformMatrix4fv(ModelMatrixUniformLocation, 1, GL_FALSE, ModelMatrix.m);
	glUniformMatrix4fv(ViewMatrixUniformLocation, 1, GL_FALSE, GCamer.GetViewMatrix().ToFloatPtr());
	ExitOnGLError("ERROR: Could not set the shader uniforms"); 

	glBindVertexArray(VAOID);
	ExitOnGLError("ERROR: Could not bind the VAO for drawing purposes");

	glDrawElements(GL_TRIANGLES, mesh.indexNum, GL_UNSIGNED_INT, (GLvoid*)0);
	ExitOnGLError("ERROR: Could not draw the cube");

	glBindVertexArray(0);
	glUseProgram(0);

}

int main(int argc, char* argv[])
{
	Initialize(argc, argv);

	glutMainLoop();
	
	exit(EXIT_SUCCESS);
}
